{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "点表示空间中的一个位置。(2,-1)  \n",
    "向量是一个表示位置改变的对象，向量没有固定的位置，默认都是将原点作为出发点。  \n",
    "以列的方式表示向量：[2,4]T（向右移动2个单位，向上移动4个单位）  \n",
    "这门课程的目标是为你提供可以立即运用到 OMS 问题中的工具。  \n",
    "你将编写几个函数并放入库中，帮助你解决二维或三维空间里的几何问题：例如，算出两个点的中点或算出点和线之间的最短距离。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* [一维向量](#一维向量)  \n",
    "  * [向量运算](#向量运算)  \n",
    "  * [大小和方向](#大小和方向)  \n",
    "  * [内积（点积）](#内积（点积）)  \n",
    "  * [平行和正交向量](#平行和正交向量)  \n",
    "  * [向量投影](#向量投影)  \n",
    "  * [向量积](#向量积)  \n",
    "* [二维空间里的直线](#二维空间里的直线)  \n",
    "* [三维平面](#三维平面)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from math import sqrt, acos, pi\n",
    "from decimal import Decimal, getcontext\n",
    "\n",
    "getcontext().prec = 30\n",
    "\n",
    "class Vector(object):\n",
    "\n",
    "    CANNOT_NORMALIZE_ZERO_VECTOR_MSG = 'Cannot normalize the zero vector'\n",
    "    NO_UNIQUE_PARALLEL_COMPONENT_MSG = 'no unique parallel component'\n",
    "    NO_UNIQUE_ORTHOGONAL_COMPONENT_MSG = 'no unique orthogonal component'\n",
    "\n",
    "    # init():用于初始化对象，在创建新对象时调用\n",
    "    def __init__(self, coordinates):\n",
    "        try:\n",
    "            if not coordinates:\n",
    "                raise ValueError\n",
    "            # Decimal()确保所有坐标都是小数，而不是浮点数或整数\n",
    "            self.coordinates = tuple([Decimal(x) for x in coordinates])\n",
    "            self.dimension = len(self.coordinates) # 向量维度\n",
    "\n",
    "        except ValueError:\n",
    "            raise ValueError('The coordinates must be nonempty')\n",
    "\n",
    "        except TypeError:\n",
    "            raise TypeError('The coordinates must be an iterable')\n",
    "\n",
    "\n",
    "    def plus(self, v):\n",
    "        new_coordinates = [x+y for x,y in zip(self.coordinates, v.coordinates)]\n",
    "        return Vector(new_coordinates)\n",
    "\n",
    "\n",
    "    def minus(self, v):\n",
    "        new_coordinates = [x-y for x,y in zip(self.coordinates, v.coordinates)]\n",
    "        return Vector(new_coordinates)\n",
    "\n",
    "\n",
    "    def time_scalar(self, c): # 数乘\n",
    "        new_coordinates = [Decimal(c)*x for x in self.coordinates]\n",
    "        return Vector(new_coordinates)\n",
    "\n",
    "\n",
    "    def magnitude(self): # 计算向量的长度\n",
    "        coordinates_squared = [x**2 for x in self.coordinates]\n",
    "        return sqrt(sum(coordinates_squared))\n",
    "\n",
    "\n",
    "    def normalized(self): # 单位向量\n",
    "        try:\n",
    "            magnitude = self.magnitude()\n",
    "            return self.time_scalar(1./magnitude)\n",
    "\n",
    "        except ZeroDivisionError:\n",
    "            raise Exception(self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG)\n",
    "\n",
    "\n",
    "    def dot(self, v): # 计算点积\n",
    "        return sum([x*y for x,y in zip(self.coordinates, v.coordinates)])\n",
    "\n",
    "\n",
    "    def angle_with(self, v, in_degrees=False): # 计算向量角度\n",
    "        try:\n",
    "            u1 = self.normalized()\n",
    "            u2 = v.normalized()\n",
    "            # The value of input for acos range from -1 <= x <= 1\n",
    "            dot_value = 1 if (abs(u1.dot(u2) - Decimal('1.'))) <= 1e-10 else u1.dot(u2)\n",
    "            angle_in_radians = acos(dot_value) # 将点积传入反余弦函数中\n",
    "            ''' 方法二：\n",
    "            vector_dot = self.dot(v) # decimal\n",
    "            multi_magni = Decimal(self.magnitude() * v.magnitude())\n",
    "            angle_in_radians = acos(vector_dot / multi_magni)\n",
    "            '''\n",
    "            if in_degrees: # 角度\n",
    "                degrees_per_radian = 180. / pi\n",
    "                return angle_in_radians * degrees_per_radian\n",
    "            else: # 弧度\n",
    "                return angle_in_radians\n",
    "\n",
    "        except Exception as e: # 检查是否为零向量\n",
    "            if str(e) == self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG:\n",
    "                raise Exception('Cannot compute an angle with the zero vector')\n",
    "            else:\n",
    "                raise e\n",
    "\n",
    "\n",
    "    def is_zero_vector(self, tolerance=1e-10): # 零向量\n",
    "        # 由于精度问题，确保点积的值非常小就行\n",
    "        return self.magnitude() < tolerance\n",
    "\n",
    "\n",
    "    def is_parallel(self, v): # 平行\n",
    "        return (self.is_zero_vector() or v.is_zero_vector() or\n",
    "                self.angle_with(v) == 0 or self.angle_with(v) == pi)\n",
    "\n",
    "\n",
    "    def is_orthogonal(self, v, tolerance=1e-10): # 垂直\n",
    "        return (abs(self.dot(v)) < tolerance)\n",
    "\n",
    "\n",
    "    def component_parallel_to(self, basis): # 平行分量\n",
    "        try:\n",
    "            u = basis.normalized()\n",
    "            weight = self.dot(u)\n",
    "            return u.time_scalar(weight)\n",
    "\n",
    "        except Exception as e:\n",
    "            if str(e) == self.CANNOT_NORMALIZE_ZERO_VECTOR_MSG:\n",
    "                raise Exception(self.NO_UNIQUE_PARALLEL_COMPONENT_MSG)\n",
    "            else:\n",
    "                raise e\n",
    "\n",
    "\n",
    "    def component_orthogonal_to(self, basis): # 垂直分量\n",
    "        try:\n",
    "            projection = self.component_parallel_to(basis)\n",
    "            return self.minus(projection)\n",
    "\n",
    "        except Exception as e:\n",
    "            if str(e) == self.NO_UNIQUE_PARALLEL_COMPONENT_MSG:\n",
    "                raise Exception(self.NO_UNIQUE_ORTHOGONAL_COMPONENT_MSG)\n",
    "            else:\n",
    "                raise e\n",
    "\n",
    "\n",
    "    def cross(self, v): # 向量积\n",
    "        try:\n",
    "            x1, y1, z1 = self.coordinates\n",
    "            x2, y2, z2 = v.coordinates\n",
    "            new_coordinates = [  y1*z2 - y2*z1 ,\n",
    "                               -(x1*z2 - x2*z1),\n",
    "                                 x1*y2 - x2*y1  ]\n",
    "            return Vector(new_coordinates)\n",
    "\n",
    "        except ValueError as e:\n",
    "            msg = str(e)\n",
    "            # 如果是二维向量，就向每个向量中添加为0的z轴坐标\n",
    "            if msg == 'need more than 2 values to unpack':\n",
    "                self_embedded_in_R3 = Vector(self.coordinates + ('0',))\n",
    "                v_embedded_in_R3 = Vector(v.coordinates + ('0',))\n",
    "                return self_embedded_in_R3.cross(v_embedded_in_R3)\n",
    "            elif (msg == 'too many values to unpack' or \n",
    "                  msg == 'need more than 1 value to unpack'):\n",
    "                raise Exception(self.ONLY_DEFINED_IN_TWO_THREE_DIMS_MSG)\n",
    "            else:\n",
    "                raise e\n",
    "\n",
    "\n",
    "    def area_of_parallelogram_with(self, v):\n",
    "        cross_product = self.cross(v)\n",
    "        return cross_product.magnitude()\n",
    "\n",
    "\n",
    "    def area_of_triangle_with(self, v):\n",
    "        return self.area_of_parallelogram_with(v) / Decimal('2.0')\n",
    "\n",
    "\n",
    "    # str():在使用print语句时被调用\n",
    "    def __str__(self):\n",
    "        return 'Vector: {}'.format(self.coordinates)\n",
    "\n",
    "\n",
    "    # eq():判断self对象是否等于对象v\n",
    "    def __eq__(self, v):\n",
    "        return self.coordinates == v.coordinates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "小数在Python自带的float类型有很大的误差，就需要**Decimal模块**来处理。\n",
    "浮点数存在不精确的性质, 而Decimal基于十进制去储存数字, 会分别记录每个十进制位的数字, 在精确要求高的场景下使用, 比如金融, 会计领域。\n",
    "\n",
    "可以传递给Decimal整型或者字符串参数，但不能是浮点数据，因为浮点数据本身就不准确。\n",
    "一般是str类型转为decimal类型，而不是float直接转。decimal类型和float类型不能做运算。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一维向量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.667\n",
      "0.111111111111111111111111111111\n",
      "0.1111\n"
     ]
    }
   ],
   "source": [
    "import decimal\n",
    "y = sqrt(Decimal(2)) # float类型\n",
    "x = Decimal(2) / Decimal(3) # Decimal(2/3)\n",
    "print(x.quantize(Decimal('0.000'))) # 四舍五入\n",
    "\n",
    "###默认30位\n",
    "decimal.getcontext()\n",
    "d=Decimal(1)/Decimal(9)\n",
    "print(d)\n",
    "\n",
    "###改成4位\n",
    "decimal.getcontext().prec = 4\n",
    "e=Decimal(1)/Decimal(9)\n",
    "print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector: (Decimal('1'), Decimal('2'), Decimal('3'))\n",
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "my_vector = Vector([1, 2, 3]) # 调用__init__\n",
    "print(my_vector) # 调用__str__\n",
    "\n",
    "my_vector2 = Vector([1, 2, 3])\n",
    "my_vector3 = Vector([-1, 2, 3])\n",
    "print(my_vector == my_vector2) # 调用__eq__\n",
    "print(my_vector == my_vector3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 向量运算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector: (Decimal('4'), Decimal('6'), Decimal('8'))\n",
      "Vector: (Decimal('-2'), Decimal('-2'), Decimal('-2'))\n",
      "Vector: (Decimal('2.5'), Decimal('5.0'), Decimal('7.5'))\n"
     ]
    }
   ],
   "source": [
    "# addition - plus\n",
    "# subtraction - minus\n",
    "# scalar multiplication - scalar multiply\n",
    "\n",
    "v = Vector([1, 2, 3])\n",
    "w = Vector([3, 4, 5])\n",
    "c = 2.5\n",
    "print(v.plus(w))\n",
    "print(v.minus(w))\n",
    "print(v.time_scalar(c))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 大小和方向"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "向量的大小和方向是指移动的距离和方向。  \n",
    "寻找与给定向量指向同一方向的单位向量，这一过程称作标准化。  \n",
    "标准化的步骤：\n",
    "1. 算出给定向量 V 的长度 v（勾股定理）。\n",
    "2. 然后将向量 V 乘以 1/v 得到单位向量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.7416573867739413\n",
      "Vector: (Decimal('0.2673'), Decimal('0.5345'), Decimal('0.8018'))\n"
     ]
    }
   ],
   "source": [
    "v = Vector([1, 2, 3])\n",
    "print(v.magnitude())\n",
    "print(v.normalized())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 内积（点积）\n",
    "inner product / dot product"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Image Name](https://cdn.kesci.com/upload/image/p9qotslp9w.png?imageView2/0/w/500/h/500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# -1 <= cos(x) <= 1\n",
    "# so: V*W = ||V||*||W||*cos(x)\n",
    "# so: -||V||*||W|| <= V*W <= ||V||*||W||\n",
    "# so: |V*W| <= ||V||*||W||\n",
    "# V*V = ||V||*||V||"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "代码里加`Decimal()`的原因是：如果两个向量指向同一方向，它们的点积是二者大小的点积。所以如果self和v指向同一方向，它们的标准化单位向量的点积应该为1,，但有时因为计算精度丢失，使得比例大于1，这样就会在acos()中产生范围错误。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "90.0\n"
     ]
    }
   ],
   "source": [
    "v = Vector([2, 0])\n",
    "w = Vector([0, 2])\n",
    "print(v.dot(w))\n",
    "print(v.angle_with(w, in_degrees=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 平行和正交向量"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一个向量可以通过数乘得到另一个向量，那这两个向量是平行(parallel)的。向量和本身是平行向量。  \n",
    "如果两个向量的点积是0，那这两个向量是正交(orthogonal)的。V\\*W=0有两种情况：V,W存在零向量；V,W互成直角。  \n",
    "零向量是所有向量的平行向量和正交向量。零向量是唯一和自己正交的向量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "v = Vector([2,4])\n",
    "w = Vector([1,2])\n",
    "print(v.is_parallel(w))\n",
    "print(v.is_orthogonal(w))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 向量投影"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525995662488_27758.png?imageView2/0/w/600/h/600)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525995671769_57060.png?imageView2/0/w/960/h/960)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector: (Decimal('1.082'), Decimal('2.671'))\n",
      "Vector: (Decimal('1.957'), Decimal('-0.7920'))\n"
     ]
    }
   ],
   "source": [
    "v = Vector([3.039, 1.879])\n",
    "w = Vector([0.825, 2.036])\n",
    "print(v.component_parallel_to(w))\n",
    "print(v.component_orthogonal_to(w))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 向量积"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "v和w的向量积是与v和w都相交的**向量**。  \n",
    "向量积的方向满足右手定理，VxW的方向：右手大拇指指向V的方向，食指指向W的方向，中指指向就是向量积的方向。  \n",
    "所以可以得到：VxW = -(WxV)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525995694592_82328.png?imageView2/0/w/640/h/640)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525995705194_98846.png?imageView2/0/w/640/h/640)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525995715304_74272.png?imageView2/0/w/640/h/640)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector: (Decimal('-11.21'), Decimal('-97.61'), Decimal('-105.7'))\n",
      "144.29137188342204\n"
     ]
    }
   ],
   "source": [
    "v = Vector([8.462, 7.893, -8.187])\n",
    "w = Vector([6.984, -5.975, 4.778])\n",
    "print(v.cross(w))\n",
    "print(v.area_of_parallelogram_with(w))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 二维空间里的直线"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "几何对象通常定义为一组坐标满足特定关系（例如方程式）的点集合。  \n",
    "直线和平面等对象满足一种特殊的方程式，叫做线性方程。只允许加减变量和常量，以及变量乘以常量。  \n",
    "在现实生活中，这些方程式通常是通过观察或数量之间的建模得出的。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 二维空间里的直线可以用两个基本属性来定义：基准点和方向向量。\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526254588209_75933.png?imageView2/0/w/420/h/420)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525995760291_79164.png?imageView2/0/w/500/h/500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525996767178_21529.png?imageView2/0/w/500/h/500)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525996776226_7139.png?imageView2/0/w/500/h/500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果画一个从原点到该直线上另一个点(x,y)的向量，那么对应的向量(x,y)将为该直线的方向向量。  \n",
    "将该等式的左侧重写为两个向量的点积，表示仅仅当该直线的方向向量(x,y)与向量(A,B)正交时，点(x,y)才位于该直线上。  \n",
    "所以可以通过找到与向量(A,B)相交的向量 **(B,-A)** ，得到该直线的方向向量。  \n",
    "\n",
    "k不等于0，方向向量不会改变.\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525997469580_36875.png?imageView2/0/w/550/h/550)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "该公式还可以快速得出与该直线相交的向量，即该直线的**法向量(A,B)**。\n",
    "由法向量很容易得到方向向量(B,-A)，法向量比方向向量更容易类推到多维空间。\n",
    "如果两条直线的法向量平行，则这两条直线平行。\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1525997921662_47006.png?imageView2/0/w/500/h/500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 求相交直线的交点\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/upload/image/p9qr3q146.png?imageView2/0/w/500/h/500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 得到两条线的交点：\n",
    "# if 法向量平行:\n",
    "#     if 两条直线重合(两条直线上各取一点相连，构成的直线与法向量垂直):\n",
    "#         是同一条直线，重合线，无限个交点\n",
    "#     else:\n",
    "#         平行，不同的直线，没有交点\n",
    "# else:\n",
    "#     相交，有一个唯一交点"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 三维平面"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "三维空间的平面和二维空间的直线非常相似。  \n",
    "定义二维空间的直线：Ax + By = k；  \n",
    "定义三维空间的平面：Ax + By + Cz = k；  \n",
    "由[A,B,C]*[x,y,z]=0可知，(A,B,C)是该平面的法向量，更改常量k的值，会沿法向量的方向移动平面。法向量平行的两个平面平行。  \n",
    "如果两个平面平行，即具有平行的法向量，可以在这两个平面上各找一个点，构成向量。如果该向量和法向量正交，则这两个平面重合。  \n",
    "\n",
    "线性方程里的变量系数，给出了所定义的线性对象，对应的法向量的坐标。这一规律可以类推到多维空间。  \n",
    "至少需要由两个变量形成的两条直线来获得直线的唯一交点。至少需要由三个变量形成的三个平面来获得平面的唯一交点。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 计算两个平面的相交线\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526395874726_99547.png?imageView2/0/w/500/h/500)\n",
    "计算两个平面法向量的向量积\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526396227245_51238.png?imageView2/0/w/600/h/600)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526426931623_5535.png?imageView2/0/w/540/h/540)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526426941930_11854.png?imageView2/0/w/540/h/540)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526427051550_6862.png?imageView2/0/w/540/h/540)\n",
    "\n",
    "通常由一定数量的变量形成的单个方程将定义维度空间比变量数量少一的线性对象。  \n",
    "具有三个变量，但其中一个系数为0，定义的是三维空间里的直线。  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 高斯消去法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "简化方程组，并在理想情况下找到唯一解，即三个平面的交点。  \n",
    "高斯消去法（Gaussian elimination）基本原理：在后续方程中逐步消去变量。  \n",
    "即第一个等式含有x,y,z，第二个等式将只含有y,z，第三个等式将只含有z。然后可以逐步算出z,y,x的值。  \n",
    "\n",
    "目前的方程组表示的平面和变换之前的方程组表示的平面并不一样，不过这些平面的交集依然不变。  \n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526478868623_99038.png?imageView2/0/w/500/h/500)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526478879217_27015.png?imageView2/0/w/500/h/500)\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526478888064_18651.png?imageView2/0/w/500/h/500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 高斯消去法的特殊案例\n",
    "（只考虑三维空间）：  \n",
    "1、方程组在高斯消去的过程中出现某个方程被简化成：0=1 这种情况，说明该方程组无解（inconsistent）。  \n",
    "2、如果某个方程被简化成：0=0 这种情况，说明该等式本来就是多余的，该方程组实际上只定义了两个平面，解不会是一个点，而是一条直线。  \n",
    "对于第二种情况，将寻找主变量（pivot variable），主变量是值由非主变量(自由变量)的值决定的变量。  \n",
    "主变量是每个方程中的首项（前提是方程组化已为三角形式），比如第一个方程的x和第二个方程的y。z则是自由变量，将成为解集参数化的参数。  \n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526481452083_22003.png?imageView2/0/w/500/h/500)\n",
    "\n",
    "然后继续高斯消去法，将主变量的系数化为1。  \n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526481522377_52341.png?imageView2/0/w/500/h/500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 参数化解集\n",
    "(x,y,z)是解集中的一个点，可以用z表示该点，这表示对于每个z值，这个点都在解集中。  \n",
    "并写成参数化直线的形式，可得到基准点和方向向量。  \n",
    "可以通过设定不同的z值，找出解集中的一些点，无论z的值是多少，得到的点都是原始方程组的解。  \n",
    "\n",
    "每个自由变量，都将成为解集的独立参数，解集的维度等于自由变量的个数。\n",
    "\n",
    "![Image Name](https://cdn.kesci.com/user_upload/image/1526482109448_74345.png?imageView2/0/w/500/h/500)\n",
    "\n",
    "此时基准点是常量向量（-1/2, 3/4, 0）；方向向量是（1, -2, 1），如果有多个，则进行相乘。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "证明方程组无解的条件：  \n",
    "1、方程组定义的平面没有交点；（充分必要条件）  \n",
    "**2、进行高斯消去运算时，出现0=k的情况（k是非零数）；（充分必要条件）**  \n",
    "\n",
    "证明方程组有唯一解的条件：  \n",
    "1、方程组定义的平面具有共同交点；（必要条件）  \n",
    "2、在高斯消去运算过程中，没有遇到0=0或0=k的情况（k是非零数）；（不充分不必要条件）  \n",
    "3、方程组里至少有三个方程；（必要条件）  \n",
    "**4、形成三角型后，每个变量都是一个方程中的首项变量（没有出现0=k的情况）；（充分必要条件）**  \n",
    "\n",
    "证明方程组有无数解的条件：  \n",
    "**1、形成三角型后，至少有一个变量不是任何等式的首项变量（没有出现0=k的情况）；（充分必要条件）**  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "要编写高斯消去法首先需要编写消去过程中用到的三个基本运算：  \n",
    "1、交换两个等式  \n",
    "2、将等式乘以非零数字  \n",
    "3、将多倍的等式加到另一个等式上  \n",
    "\n",
    "高斯消去法：  \n",
    "第一步：将方程组变成三角型；  \n",
    "1、可以存在多种有效的三角型，比如用哪行与具有零系数的行交换。测试用例的答案假设具有零系数的行与其下方具有非零系数的最顶行交换。  \n",
    "2、不要使用将系数与行相乘的运算。  \n",
    "3、仅将几倍的某行与其下方(而不是上方)的行相加。  \n",
    "第二步：将三角型化为最简梯阵型方程组（RREF）  \n",
    "1、每个主变量的系数为1，且只存在于一个方程中。  \n",
    "2、如果出现0=k(k不为0)，则k要化为1，而且该式必须在0=0前。\n",
    "\n",
    "得出方程组的结果：  \n",
    "1、唯一解(向量对象)  \n",
    "2、无解  \n",
    "3、无数解（给出解集的参数化形式）\n",
    "\n",
    "如果是无数解，则需要写输出参数化对象的程序，该对象表示有无数个解的方程的解。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
